<?php
class Job
{
  const COMPRESSION_MODE_GZIP               = "gzip";
  const COMPRESSION_MODE_TARGZIP            = "targzip";
  
  const MODE_EXEC                           = "exec";
  const MODE_TEST                           = "test";
  
  protected $activeFrom                     = "00:00";
  protected $activeTo                       = "23:59";
  protected $applianceName                  = null;
  protected $backupFilesize                 = -1;
  protected $backupFilesizeMaximum          = -1;
  protected $backupFilesizeMinimum          = 0;
  protected $backupPath                     = null;
  protected $compressionMode                = null;
  protected $destinationBackup              = null;
  protected $destinationCompression         = null;
  protected $enabled                        = true;
  protected $expiresAfter                   = 3;
  protected $host                           = null;
  protected $lastBackupCompressionFilesize  = null;
  protected $lastBackupTime                 = null;
  protected $logger                         = null;
  protected $mode                           = null;
  protected $name                           = null;
  protected $notifier                       = null;
  protected $password                       = null;
  protected $pathMysqlBin                   = "";
  protected $pathXe                         = "";
  protected $pathSendEmail                  = "";
  protected $preserve                       = 20;
  protected $status                         = null;
  protected $type                           = null;
  protected $user                           = null;
  
  public function __construct()
  {
    $this->logger = Logger::getRootLogger();
    $this->notifier = Notifier::getNotifier();
  }
    
  public function doCleanup()
  {
    if(!is_dir($this->getFullBackupPath()))
    {
      $this->logger->warn("Info: Directory '" . $this->getFullBackupPath() . "' doesn't exists, could not cleanup.");
      return;
    }
    
    $fileCount = 0;
    $lastMonth = -1;
    $files = scandir($this->getFullBackupPath(), "1");
    foreach($files as $file)
    {
      if(substr($file, 0, 1)==".")
      {
        continue;
      }
      
      if(strpos($file, $this->host)>-1 && strpos($file, $this->getFileIdentifier())>-1)
      {
        $date = substr($file, 0, 8);
        if(ctype_digit($date))
        {
          $fileCount++;
          
          $monthFile = intval(substr($date, 4, 2));
          
          if($lastMonth==-1)
          {
            $lastMonth = $monthFile;
          }
          
          if($fileCount<$this->preserve)
          {
            continue;
          }          
          
          if($monthFile!=$lastMonth)
          {
            $lastMonth = $monthFile;
            continue;
          }
          
          $cmds = array();
          
          switch(PHP_OS)
          {
            case "Linux":
              $cmds[] = "rm -f " . $this->getFullBackupPath() . $file;
              break;
              
            case "WINNT":
              $cmds[] = "del " . $this->getFullBackupPath() . $file;
              break;
              
            default:
              $this->logger->warn("Can not cleanup unsupported OS '" . PHP_OS . "'.");
              break;
          }
          
          $this->execCommandos($cmds);
        }
      }
    }
  }  
  
  public function doCompress()
  {
    $cmds = array();
    
    switch(PHP_OS)
    {
      case "Linux":
        switch ($this->compressionMode)
        {
          case Job::COMPRESSION_MODE_GZIP:
            $cmds[] = "nice -n 19 gzip " . $this->getDestinationBackup();
            break;
            
          case Job::COMPRESSION_MODE_TARGZIP:
            $cmds[] = "nice -n 19 tar --remove-files -c -z -f " . $this->getDestinationCompression() . " " . $this->getDestinationBackup();
            $cmds[] = "rm -rf " . $this->getDestinationBackup();
            break;
        }
        break;
        
      default:
        $this->logger->warn("Can not compress unsupported OS '" . PHP_OS . "'.");
        break;
    }
    
    $this->execCommandos($cmds);
  }
  
  protected function execCommando($cmd)
  {
    $this->logger->info("Execute: '" . str_replace($this->password, "*******", $cmd) . "'");
    
    $output = array();
    $returnValue = null;
    if($this->mode!=self::MODE_TEST)
    {
      exec($cmd, $output, $returnValue);
    }

    if($returnValue===0)
    {
      $this->logger->info("Return code: '" . $returnValue . "'");
    }
    else
    {
      $subject = "Error on execution of command '" . substr($cmd, 0, strpos($cmd, " ")) . "', return value: " . $returnValue;
      $message = 
      "Error on execution of command:\\n" . 
      $cmd . "\\n" .
      "Return Value: " . $returnValue . "\\n" .
      "\\n" .
      "Output:\\n" .
      implode("\\n", $output);      
      
      $this->status = new JobStatus(JobStatus::ERROR, $subject);
      $this->notifier->send($subject, $message);
      $this->logger->error("Return code: '" . $returnValue . "' Output: '" . implode("\\n", $output) . "'");
    }
    return $output;
  }
  
  protected function execCommandos($cmds)
  {
    if(count($cmds)>0)
    {
      foreach($cmds as $cmd)
      {
        $this->execCommando($cmd);
      }
    }
  }
  
  public function getActiveFrom()
  {
    return $this->activeFrom;
  }
  
  public function getActiveTo()
  {
    return $this->activeTo;
  }
  
  public function getApplianceName()
  {
    return $this->applianceName;
  }
  
  public function getDestinationBackup()
  {
    if($this->destinationBackup==null)
    {
      $this->destinationBackup = $this->getFullBackupPath() . date("Ymd") . "-" . date("His") . "-" . $this->host . "-" . $this->getFileIdentifier() . $this->getFileExtensionBackup();
    }
    
    return $this->destinationBackup;
  }
  
  public function getDestinationBackupFilesize($target)
  {
    $filesize = 0;
    if(is_dir($target))
    {
      $entries = scandir($target);
      foreach($entries as $entry)
      {
        if($entry=="." || $entry=="..")
        {
          continue;
        }
        
        if(is_dir($target . DIRECTORY_SEPARATOR . $entry))
        {
          $filesize+= $this->getDestinationBackupFilesize($target . DIRECTORY_SEPARATOR . $entry);
        }
        
        if(is_file($target . DIRECTORY_SEPARATOR . $entry))
        {
          $filesize+= $this->getFileSize($target . DIRECTORY_SEPARATOR . $entry);
        }
      }
    }
    else if(is_file($target))
    {
          $filesize+= $this->getFileSize($target);
    }
    
    $this->backupFilesize = $filesize;
  }
  
  public function getDestinationCompression()
  {
    if($this->destinationCompression==null)
    {
      $this->destinationCompression = $this->getFullBackupPath() . date("Ymd") . "-" . date("His") . "-" . $this->host . "-" . $this->getFileIdentifier() . $this->getFileExtensionCompression();
    }
    
    return $this->destinationCompression;
  }
  
  protected function getFileSize($file)
  {
    switch(PHP_OS)
    {
      case "Linux":
        return trim(shell_exec("stat -c %s " . $file));
        break;
        
      case "WINNT":
        return trim(shell_exec("for %I in (" . $file .") do @echo %~zI"));
        break;
        
      default:
        $this->logger->warn("Unsupported OS " . PHP_OS . " can not detect the filesize of the backup.");
        return 0;
        break;
    }
  }
  
  protected function getFullBackupPath()
  {
    return $this->backupPath . $this->applianceName . DIRECTORY_SEPARATOR . $this->type . DIRECTORY_SEPARATOR;
  }
  
  public function getHost()
  {
    return $this->host;
  }
  
  public function getLastBackupInformation()
  {
    if(!is_dir($this->getFullBackupPath()))
    {
      $this->logger->info("Directory '" . $this->getFullBackupPath() . "' doesn't exists, could not check if last backup is expired.");
      return true;
    }
    
    $files = scandir($this->getFullBackupPath(), "1");
    if(count($files)>2)
    {
      $file = null;
      foreach($files as $backupFile)
      {
        if($this->isJobBackupFile($backupFile))
        {
          $file = $backupFile;
          break;
        }
      }
      
      if($file==null)
      {
        $this->logger->info("Found no backup file.");
        return true;
      }
      
      $date = substr($file, 0, 8);
      $time = substr($file, 9, 6);
      
      $this->lastBackupTime = mktime(substr($time, 0, 2), substr($time, 2, 2), substr($time, 4, 2), substr($date, 4, 2), substr($date, 6, 2), substr($date, 0, 4));
      $this->lastBackupCompressionFilesize = $this->getFileSize($this->getFullBackupPath() . $file);
    }
  }
  
  public function getLastBackupCompressionFilesize()
  {
    return $this->lastBackupCompressionFilesize;
  }
  
  public function getLastBackupTime($format)
  {
    if($this->lastBackupTime==null)
    {
      return "unknown";
    }
    return date($format, $this->lastBackupTime);
  }
  
  public function getName()
  {
    return $this->name;
  }  
  
  public function getStatus()
  {
    if($this->status==null)
    {
      return new JobStatus(JobStatus::WARNING, "unknown");
    }
    
    return $this->status;
  }
  
  public function getType()
  {
    return $this->type;
  }
  
  public function isActiveTime()
  {
    $now = mktime(date("H"), date("i"), 30, 1, 1, 2000);
    
    $fromParts = explode(":", $this->activeFrom);
    $toParts = explode(":", $this->activeTo);

    $offsetDay = 0;
    if($toParts<$fromParts)
    {
      $offsetDay = 1;
    }
    
    $from = mktime(intval($fromParts[0]), intval($fromParts[1]), 1, 1, 1, 2000);
    $to = mktime(intval($toParts[0]), intval($toParts[1]), 59, 1, (1+$offsetDay), 2000);
    
    if($from < $now && $now < $to)
    {
      return true;
    }
    
    $message = "Job is not active now. It is active between " . $this->getActiveFrom() . "-" . $this->getActiveTo() . ".";
    $this->status = new JobStatus(JobStatus::INFO, $message);
    $this->logger->info($message);
    return false;
  }
  
  public function isDisabled()
  {
    if($this->enabled==true)
    {
      return false;
    }
    
    $message = "Job disabled";
    $this->status = new JobStatus(JobStatus::INFO, $message);
    $this->logger->info($message);
    return true;
  }
  
  public function isJobBackupFile($file)
  {
    if(strpos($file, $this->host)>-1 && strpos($file, $this->getFileIdentifier())>-1)
    {
      return true;
    }
    
    return false;
  }
  
  public function isLastBackupExpired()
  {
      $expireTime = $this->lastBackupTime + mktime($this->expiresAfter, 0, 0, 1, 1, 1970);
      //die(var_dump($expireTime));
      $now = mktime(date("H"), date("i"), date("s"), date("n"), date("j"), date("Y"));
      
      if($now-$expireTime>0)
      {
        $this->logger->info("Last backup is expired on " . date("Y-m-d H:i:s", $expireTime) . ".");
        return true;
      }
      else
      {
        $message = "Last backup expires on " . date("Y-m-d H:i:s", $expireTime) . ".";
        $this->status = new JobStatus(JobStatus::SUCCESSFUL, $message) ;
        $this->logger->info($message);
        return false;
      }
    
    $this->logger->info("Can not detect if last backup is expired.");
    return true;
  }
  
  public function setActiveFrom($activeFrom)
  {
    $activeFrom = trim($activeFrom);
    $pattern = '/^[0-9]{2}:[0-9]{2}$/';
    if(preg_match($pattern, $activeFrom))
    {
      $this->activeFrom = $activeFrom;
    }
  }
  
  public function setActiveTo($activeTo)
  {
    $activeTo = trim($activeTo);
    $pattern = '/^[0-9]{2}:[0-9]{2}$/';
    if(preg_match($pattern, $activeTo))
    {
      $this->activeTo = $activeTo;
    }
  }  
  
  public function setBackupFilesizeMaximum($filesize)
  {
    $filesize = trim($filesize);
    $pattern = '/^[0-9]+$/';
    if(preg_match($pattern, $filesize))
    {
      $this->backupFilesizeMaximum = $filesize;
    }
  }
  
  public function setBackupFilesizeMinimum($filesize)
  {
    $filesize = trim($filesize);
    $pattern = '/^[0-9]+$/';
    if(preg_match($pattern, $filesize))
    {
      $this->backupFilesizeMinimum = $filesize;
    }
  }  
  
  public function setBackupProperty($name, $value)
  {
    $name = trim($name);
    
    $obj = new ReflectionObject($this);
    if($obj->hasProperty($name))
    {
      $value = trim($value);
      $this->$name = $value;
    }
    else
    {
      $this->logger->warn("Can not set job property '" . $name . "', it is unknown.");
    }
  }
  
  public function setCompressionMode($compressionMode)
  {
    $compressionMode = trim($compressionMode);
    
    switch(strtolower($compressionMode))
    {
      case Job::COMPRESSION_MODE_GZIP:
        $this->compressionMode = Job::COMPRESSION_MODE_GZIP;
        break;
        
      case Job::COMPRESSION_MODE_TARGZIP:
        $this->compressionMode = Job::COMPRESSION_MODE_TARGZIP;
        break;
    }
  }
  
  public function setEnabled($enabled)
  {
    $enabled = trim($enabled);
    
    if($enabled=="false")
    {
      $this->enabled = false;
    }
  }
  
  public function setExpiresAfter($expiresAfter)
  {
    if(substr($expiresAfter, -1)=="h")
    {
      $hours = substr($expiresAfter, 0, -1);
      if(ctype_digit($hours))
      {
        $this->expiresAfter = $hours + intval(date("I"));
      }
    }
  }
  
  public function setName($name)
  {
    if($name!=null)
    {
      $this->name = trim($name);
    }
  }
  
  public function setHost($host)
  {
    $host = trim($host);
    
    $this->host = $host;
  }
  
  public function setPassword($password)
  {
    $password = trim($password);
    
    $this->password = $password;
  }
  
  public function setPreserve($preserve)
  {
    $preserve = trim($preserve);
    
    if(ctype_digit($preserve))
    {
      $this->preserve = $preserve;
    }
  }
  
  public function setUser($user)
  {
    $user = trim($user);
    
    $this->user = $user;
  }
  
  public function validBackup()
  {
    $this->getDestinationBackupFilesize($this->getDestinationBackup());
    
    if($this->backupFilesizeMinimum < $this->backupFilesize && $this->backupFilesize < $this->backupFilesizeMaximum)
    {
      $message = "The size of the backup (before compression) is '" . $this->backupFilesize . "'.";
      if($this->status==null)
      {
        $this->status = new JobStatus(JobStatus::SUCCESSFUL, $message);
      }
      $this->logger->info($message);
      return true;
    }
    
    $message =
    "Jobname: " . $this->name . "\n".
    "Jobtype: " . $this->type . "\n".
    "\n".
    "The size (" . $this->backupFilesize . ") of the backup (before compression) is not between '" . $this->backupFilesizeMinimum . "' and '" . $this->backupFilesizeMaximum ."'";
    $this->status = new JobStatus(JobStatus::ERROR, $message);
    $this->logger->error($message);
    $this->notifier->send("Jobname: " . $this->name . " Filesize error", $message);
    return false;
  }
}